#ifndef HEADER_fd_src_flamenco_stakes_fd_vote_states_h
#define HEADER_fd_src_flamenco_stakes_fd_vote_states_h

#include "../../util/fd_util_base.h"
#include "../../util/tmpl/fd_map.h"
#include "../types/fd_types_custom.h"

#define FD_VOTE_STATES_MAGIC (0xF17EDA2CE7601E70UL) /* FIREDANCER VOTER V0 */

/* fd_vote_states_t is a cache of vote accounts mapping the pubkey of
   a vote account to various information about the vote account
   including, stake, last vote slot/timestamp, and commission for the
   vote account.  The vote states are safe to be used across multiple
   threads but concurrent reads/writes must be synchronized by the
   caller.

   In the runtime, there are 3 instances of fd_vote_states_t that are
   maintained and used at different points, notably around epoch reward
   and leader schedule calculations. The 3 instances are:
   1. vote_states: This is the vote states for the current epoch. This
      is updated through the course of an epoch as vote accounts are
      updated.
   2. vote_states_prev: This is the vote states as of the end of
      previous epoch E-1 if we are currently executing epoch E.
      This gets updated at the end of an epoch when vote_states are
      copied into vote_states_prev.
   3. vote_states_prev_prev: This is the vote states as of the end of
      epoch E-2 if we are currently executing epoch E. This only gets
      updated at the end of an epoch when vote_states_prev is copied
      into vote_states_prev_prev.

   The implementation of fd_vote_states_t is a hash map which is backed
   by a memory pool. Callers are allowed to insert, replace, and remove
   entries from the map.

   In practice, fd_vote_states_t are updated in 3 cases:
   1. They are initially populated from the versioned vote account
      stake accounts in the snapshot manifest. These are populated from
      the raw vote account data. This is done in a single pass over the
      vote account data.
   2. The vote states for the current epoch can be updated after
      transaction execution. This is done for vote accounts that are
      referenced during a transaction.
   3. Vote states are updated at the epoch boundary. The stake
      information for the vote states is refreshed at the boundary.

   The vote states in reality manage a few different sets of
   information about the vote account:
   - vote account state: state from the vote account data including
     the last vote slot/timestamp, commission, and node pubkey.  This is
     used for clock sysvar and rewards calculations.
   - stake: stake as of the end of the previous epoch.  This is used
     eventually for leader schedule calculations.  The stake from epoch
     T-2 (stake_t_2) is used for the stake in clock calculations and Tower's threshold switch checks.
     T-1 (stake_t_1)
   - rewards: this information is only used at the epoch boundary.

   NOTE: The leader schedule epoch is almost always equal to the current
   epoch + 1.
   TODO: highlight the cases where this is not possible.

   In the agave client, each bank holds its own epoch stakes (agave
   equivalent of vote_states).  These are keyed by leader schedule epoch
   (which is almost always equal to epoch + 1).  Old entries are removed
   and new entries are inserted at the epoch boundary.  When we use the
   epoch stakes for consensus and for clock timestamp calculations, we
   query the epoch stakes by the current epoch we are executing in.

   When we cross from Epoch E-1 to E, we compute our stakes assuming
   we are in E.  At this point, because we are in epoch E, this means
   that the leader schedule epoch is E+1 so an epoch_stakes entry for
   epoch E+1 is inserted into epoch_stakes.

   After we cross the boundary, in epoch E, we use the epoch stakes as
   keyed by epoch E for clock/consensus/leader schedule calculations.
   The stakes keyed by epoch E are actually the ones calculated at the
   boundary between epoch E-2 and E-1 since that's when the leader
   schedule epoch is E.  Similiarly, when we are in epoch E+1 we use the
   stakes caclulated between epoch E-1 and E since that's when the leader
   schedule epoch is E+1.

   The firedancer naming of vote states is instead done by reference to
   the epoch boundary that the stakes were calculated in.  If we are
   currently in epoch E, we use the vote states calculated at the end of
   two epochs ago (E-2 -> E-1), hence vote_states_prev_prev.  We store
   the vote states calculated at the end of the previous epoch
   (E-1 -> E) as vote_states_prev.  This is also how we cache the stake
   values in the current vote states which are continually updated. */

#define FD_VOTE_STATES_ALIGN (128UL)

/* Agave defines the max number of epoch credits to store to be 64.
   https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v2.2.6/vote-interface/src/state/mod.rs#L37 */
#define EPOCH_CREDITS_MAX (64UL)

struct fd_vote_state_credits {
  ulong  credits_cnt;
  ushort epoch       [ EPOCH_CREDITS_MAX ];
  ulong  credits     [ EPOCH_CREDITS_MAX ];
  ulong  prev_credits[ EPOCH_CREDITS_MAX ];
};
typedef struct fd_vote_state_credits fd_vote_state_credits_t;

struct fd_vote_state_ele {
  /* Internal pool/map use */
  ulong       idx;
  ulong       next_;

  /* Vote account stake information which is derived from the stake
     delegations.  This information is used for leader schedule
     calculation and clock stake-weighted median calculations.

     stake_t_2 is used in Tower, for it's threshold switch checks. */
  ulong       stake;
  ulong       stake_t_1;
  ulong       stake_t_2;

  /* Vote account information which is derived from the vote account
     data and is used for clock timestamp calculations. */
  fd_pubkey_t vote_account;
  fd_pubkey_t node_account;
  ulong       last_vote_slot;
  long        last_vote_timestamp;
  uchar       commission;
};
typedef struct fd_vote_state_ele fd_vote_state_ele_t;

/* Forward declare map iterator API generated by fd_map_chain.c */
typedef struct fd_vote_state_map_private fd_vote_state_map_t;
typedef struct fd_map_chain_iter fd_vote_state_map_iter_t;
struct fd_vote_states_iter {
  fd_vote_state_map_t *    map;
  fd_vote_state_ele_t *    pool;
  fd_vote_state_map_iter_t iter;
};
typedef struct fd_vote_states_iter fd_vote_states_iter_t;

struct __attribute__((aligned(FD_VOTE_STATES_ALIGN))) fd_vote_states {
  ulong magic;
  ulong max_vote_accounts_;
  ulong pool_offset_;
  ulong map_offset_;
};
typedef struct fd_vote_states fd_vote_states_t;

/* This guarantees that the pool element alignment is at most 128UL. */
FD_STATIC_ASSERT(alignof(fd_vote_state_ele_t)<=FD_VOTE_STATES_ALIGN, unexpected pool element alignment);

/* The static footprint of the vote states assumes that there are
   FD_RUNTIME_MAX_VOTE_ACCOUNTS. It also assumes worst case alignment
   for each struct. fd_vote_states_t is laid out as first the
   fd_vote_states_t struct, followed by a pool of fd_vote_state_ele_t
   structs, followed by a map of fd_vote_state_map_ele_t structs.
   The pool has FD_RUNTIME_MAX_VOTE_ACCOUNTS elements, and the map
   has a chain count deteremined by a call to
   fd_vote_states_chain_cnt_est.
   NOTE: the footprint is validated to be at least as large as the
   actual runtime-determined footprint (see test_vote_states.c) */

#define FD_VOTE_STATES_CHAIN_CNT_EST (32768UL)
#define FD_VOTE_STATES_FOOTPRINT                                                      \
  /* First, layout the struct with alignment */                                       \
  sizeof(fd_vote_states_t) + alignof(fd_vote_states_t) +                              \
  /* Now layout the pool's data footprint */                                          \
  FD_VOTE_STATES_ALIGN + sizeof(fd_vote_state_ele_t) * FD_RUNTIME_MAX_VOTE_ACCOUNTS + \
  /* Now layout the pool's meta footprint */                                          \
  FD_VOTE_STATES_ALIGN + 128UL /* POOL_ALIGN */ +                                     \
  /* Now layout the map.  We must make assumptions about the chain */                 \
  /* count to be equivalent to chain_cnt_est. */                                      \
  FD_VOTE_STATES_ALIGN + 128UL /* MAP_ALIGN */ + (FD_VOTE_STATES_CHAIN_CNT_EST * sizeof(ulong))

FD_PROTOTYPES_BEGIN

/* fd_vote_states_align returns the minimum alignment required for a
   vote states struct. */

FD_FN_CONST ulong
fd_vote_states_align( void );

/* fd_vote_states_footprint returns the footprint of the vote states
   struct for a given amount of max vote accounts. */

FD_FN_CONST ulong
fd_vote_states_footprint( ulong max_vote_accounts );

/* fd_vote_states_new creates a new vote states struct with a given
   number of max vote accounts and a seed. It formats a memory region
   which is sized based off of the number of vote accounts. */

void *
fd_vote_states_new( void * mem,
                    ulong  max_vote_accounts,
                    ulong  seed );

/* fd_vote_states_join joins a vote states struct from a
   memory region. There can be multiple valid joins for a given memory
   region but the caller is responsible for accessing memory in a
   thread-safe manner. */

fd_vote_states_t *
fd_vote_states_join( void * mem );

/* fd_vote_states_update inserts or updates the vote state corresponding
   to a given account. */

fd_vote_state_ele_t *
fd_vote_states_update( fd_vote_states_t *  vote_states,
                       fd_pubkey_t const * vote_account );

/* fd_vote_states_update_from_account inserts or updates the vote state
   corresponding to a valid vote account. This is the same as
   fd_vote_states_update but is also responsible for decoding the vote
   account data into a versioned vote state object and extracing the
   commission and credits. Kills the client if the vote state cannot
   be decoded. */

fd_vote_state_ele_t *
fd_vote_states_update_from_account( fd_vote_states_t *  vote_states,
                                    fd_pubkey_t const * vote_account,
                                    uchar const *       account_data,
                                    ulong               account_data_len );

/* fd_vote_states_reset_stakes_t resets the stakes to 0 for each of the
   vote accounts in fd_vote_states_t. */

void
fd_vote_states_reset_stakes( fd_vote_states_t * vote_states );

/* fd_vote_states_remove removes the vote state corresponding to a given
   account. Does nothing if the account does not exist. */

void
fd_vote_states_remove( fd_vote_states_t *  vote_states,
                       fd_pubkey_t const * vote_account );

/* fd_vote_states_query returns the vote state corresponding to a given
   account. Returns NULL if the account does not exist. This function is
   safe for concurrent reads, but the caller needs to synchronize
   concurrent writers to the fd_vote_state_ele_t. */

fd_vote_state_ele_t *
fd_vote_states_query( fd_vote_states_t const * vote_states,
                      fd_pubkey_t const *      vote_account );

/* fd_vote_states_query_const is the same as fd_vote_states but instead
   returns a const pointer. */

fd_vote_state_ele_t const *
fd_vote_states_query_const( fd_vote_states_t const * vote_states,
                            fd_pubkey_t const *      vote_account );

/* fd_vote_states_max returns the maximum number of vote accounts that
   the vote states struct can support. */

ulong
fd_vote_states_max( fd_vote_states_t const * vote_states );

/* fd_vote_states_cnt returns the number of vote states in the vote
   states struct. */

ulong
fd_vote_states_cnt( fd_vote_states_t const * vote_states );

/* Iterator API for vote states.  The iterator is initialized with a
   call to fd_vote_states_iter_init.  The caller is responsible for
   managing the memory for the iterator.  It is safe to call
   fd_vote_states_iter_next if the result of
   fd_vote_states_iter_done() ==0.  It is safe to call
   fd_vote_states_iter_ele() to get the current vote state. As a note,
   it is safe to modify the vote state acquired from
   fd_vote_states_iter_ele() as long as the next_ field is not modified
   (which the caller should never do).  It is unsafe to insert or remove
   fd_vote_state_ele_t from the vote states struct while iterating.

   Under the hood, the iterator is just a wrapper over the iterator in
   fd_map_chain.c.

   Example use:

   fd_vote_states_iter_t iter_[1];
   for( fd_vote_states_iter_t * iter = fd_vote_states_iter_init( vote_states, iter_ ); !fd_vote_states_iter_done( iter ); fd_vote_states_iter_next( iter ) ) {
     fd_vote_state_ele_t * vote_state = fd_vote_states_iter_ele( iter );
     // Do something with the vote state ...
   }
*/

fd_vote_state_ele_t *
fd_vote_states_iter_ele( fd_vote_states_iter_t * iter );

fd_vote_states_iter_t *
fd_vote_states_iter_init( fd_vote_states_iter_t *  iter,
                          fd_vote_states_t const * vote_states );

int
fd_vote_states_iter_done( fd_vote_states_iter_t * iter );

void
fd_vote_states_iter_next( fd_vote_states_iter_t * iter );

FD_PROTOTYPES_END

#endif /* HEADER_fd_src_flamenco_stakes_fd_vote_states_h */
